/*
 * RED5 Open Source Flash Server - http://code.google.com/p/red5/
 * 
 * Copyright 2006-2014 by respective authors (see below). All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.red5.server.net.proxy;

import java.io.File;
import java.io.FileOutputStream;
import java.net.InetSocketAddress;
import java.nio.channels.WritableByteChannel;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.red5.server.net.rtmp.message.Header;
import org.red5.server.net.rtmp.message.Packet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;

public class DebugProxyHandler extends IoHandlerAdapter implements ResourceLoaderAware {

	protected static Logger log = LoggerFactory.getLogger(DebugProxyHandler.class);

	private ResourceLoader loader;

	private ProtocolCodecFactory codecFactory;

	private InetSocketAddress forward;

	private String dumpTo = "./dumps/";

	/** {@inheritDoc} */
	public void setResourceLoader(ResourceLoader loader) {
		this.loader = loader;
	}

	/**
	 * Setter for property 'codecFactory'.
	 *
	 * @param codecFactory Value to set for property 'codecFactory'.
	 */
	public void setCodecFactory(ProtocolCodecFactory codecFactory) {
		this.codecFactory = codecFactory;
	}

	/**
	 * Setter for property 'forward'.
	 *
	 * @param forward Value to set for property 'forward'.
	 */
	public void setForward(String forward) {
		int split = forward.indexOf(':');
		String host = forward.substring(0, split);
		int port = Integer.parseInt(forward.substring(split + 1, forward.length()));
		this.forward = new InetSocketAddress(host, port);
	}

	/**
	 * Setter for property 'dumpTo'.
	 *
	 * @param dumpTo Value to set for property 'dumpTo'.
	 */
	public void setDumpTo(String dumpTo) {
		this.dumpTo = dumpTo;
	}

	/** {@inheritDoc} */
	@Override
	public void sessionOpened(IoSession session) throws Exception {
		SocketSessionConfig ssc = (SocketSessionConfig) session.getConfig();
		ssc.setTcpNoDelay(true);
		//ssc.setReceiveBufferSize(2048);
		//ssc.setSendBufferSize(2048);

		super.sessionOpened(session);

	}

	/** {@inheritDoc} */
	@SuppressWarnings("resource")
	@Override
	public void sessionCreated(IoSession session) throws Exception {

		boolean isClient = session.getRemoteAddress().equals(forward);

		if (log.isDebugEnabled()) {
			log.debug("Is downstream: " + isClient);
			session.getFilterChain().addFirst("protocol", new ProtocolCodecFilter(codecFactory));
		}

		session.getFilterChain().addFirst("proxy", new ProxyFilter(isClient ? "client" : "server"));

		if (true) {

			String fileName = System.currentTimeMillis() + '_' + forward.getHostName() + '_' + forward.getPort() + '_' + (isClient ? "DOWNSTREAM" : "UPSTREAM");

			File headersFile = loader.getResource(dumpTo + fileName + ".cap").getFile();
			headersFile.createNewFile();

			File rawFile = loader.getResource(dumpTo + fileName + ".raw").getFile();
			rawFile.createNewFile();

			FileOutputStream headersFos = new FileOutputStream(headersFile);
			WritableByteChannel headers = headersFos.getChannel();

			FileOutputStream rawFos = new FileOutputStream(rawFile);
			WritableByteChannel raw = rawFos.getChannel();

			IoBuffer header = IoBuffer.allocate(1);
			header.put((byte) (isClient ? 0x00 : 0x01));
			header.flip();
			headers.write(header.buf());

			session.getFilterChain().addFirst("dump", new NetworkDumpFilter(headers, raw));
		}

		//session.getFilterChain().addLast("logger", new LoggingFilter() );

		if (!isClient) {
			log.debug("Connecting..");
			IoConnector connector = new NioSocketConnector();
			connector.setHandler(this);
			ConnectFuture future = connector.connect(forward);
			future.awaitUninterruptibly(); // wait for connect, or timeout
			if (future.isConnected()) {
				if (log.isDebugEnabled()) {
					log.debug("Connected: {}", forward);
				}
				IoSession client = future.getSession();
				client.setAttribute(ProxyFilter.FORWARD_KEY, session);
				session.setAttribute(ProxyFilter.FORWARD_KEY, client);
			}
		}
		super.sessionCreated(session);
	}

	/** {@inheritDoc} */
	@Override
	public void messageReceived(IoSession session, Object in) {
		if (log.isDebugEnabled()) {
			if (in instanceof IoBuffer) {
				log.debug("Handskake");
				return;
			}
			try {
				final Packet packet = (Packet) in;
				final Object message = packet.getMessage();
				final Header source = packet.getHeader();

				log.debug("{}", source);
				log.debug("{}", message);
			} catch (RuntimeException e) {
				log.error("Exception", e);
			}
		}
	}

	/** {@inheritDoc} */
	@Override
	public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Exception caught", cause);
		}
	}

}
